﻿using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using Microsoft.Phone.Controls;
using RStein.PosterousReader.WP.UIServices;
using System.Windows.Navigation;
using Microsoft.Phone.Shell;
using RStein.PosterousReader.Common.ViewModelsHelpers;
using System.Threading;
using System.Collections.Generic;
using RStein.PosterousReader.Common.UIHelpers;
using System.Linq;
using RStein.PosterousReader.WP.ViewModels.SpecialTypes;
using RStein.PosterousReader.WP.ViewModels;

namespace RStein.PosterousReader.WP.UI
{
  public class PageBase : PhoneApplicationPage
  {

    private List<Object> m_viewModels;    
    
    public PageBase()
    {
      IsNewInstance = true;
      WasLayoutUpdateCalled = false;
      LayoutUpdated += PageBase_LayoutUpdated;
      m_viewModels = new List<Object>();      
    }
        
    static PageBase()
    {      
      ViewModelManager = () => new ViewModelResolver();
      
    }

    public bool IsNewInstance
    {
      get;
      set;
    }

    public bool WasLayoutUpdateCalled
    {
      get;
      set;
    }
   
    public static Func<IViewModelResolver> ViewModelManager
    {
      get;
      set;
    }

    public Dictionary<String, Object> LastSavedTransientState
    {
      get;
      set;
    }

    
    protected override void OnNavigatedTo(NavigationEventArgs e)
    {
      
      base.OnNavigatedTo(e);
      WasLayoutUpdateCalled = false;
      loadSavedTranzientState();
      
    }

    protected override void OnNavigatedFrom(NavigationEventArgs e)
    {
      
      base.OnNavigatedFrom(e);  
      SaveState();
      handleAllDeactivated();

    }
    
    private T tryGetTypedViewModel<T>(object obj)
              where T: class
    {
      return obj as T;
    }

    protected virtual void SaveState()
    {

      var statebag = new KnownTypesDictionary();
      var rootPair = getRootpair();
      saveTransientState(rootPair, statebag, this, null);
      string stateKey = GetType().FullName;
      State[stateKey] = statebag;
      LastSavedTransientState = statebag;
      
    }
       
    protected virtual void LoadState()
    {

      var rootPair = getRootpair();
      
      restoreTransientState(rootPair, this, null);
      

      
    }

    private ElementIndexPair getRootpair()
    {
      var rootPair = new ElementIndexPair
      {
        Element = this,
        Parent = null,
        Index = 0
      };
      return rootPair;
    }


    private void saveTransientState(ElementIndexPair pair, Dictionary<string, Object> stateBag, FrameworkElement root, Object lastDataContext)
    {
      if (ViewModelManager().IsView(pair.Element))
      {

        var stateManager = getStateManager(pair.Element.DataContext);

        if (stateManager != null && stateManager != lastDataContext)
        {
          lastDataContext = stateManager;
          saveStateInner(pair, stateBag, root, stateManager);          
        }
      }

      UIHelper.GetChildren(pair.Element)
              .ToList()
              .ForEach(elementPair =>
                               {
                                 elementPair.Parent = pair;
                                 saveTransientState(elementPair, stateBag, root, lastDataContext);
                               });

    }

    private void saveStateInner(ElementIndexPair pair, Dictionary<string, Object> stateBag, FrameworkElement root, ITransientStateManager stateManager)
    {
      var stateKey = UIHelper.GetTransientStateKey(pair, root);
      
      var uniqueContextIdAwareModel = tryGetTypedViewModel<IUniqueHostKeyReceiver>(stateManager);
      setViewModelUniqueId(uniqueContextIdAwareModel, stateKey);
  
      var state = stateManager.SaveState();            
      
      if (state != null)
      {        
        stateBag[stateKey] = state;        
      }

      

      
    }

    private void restoreTransientState(ElementIndexPair pair, FrameworkElement root, Object lastViewModel)
    {

      if (ViewModelManager().IsView(pair.Element))
      {


        string stateKey = UIHelper.GetTransientStateKey(pair, root);
        bool hasSavedState = LastSavedTransientState != null &&
                             LastSavedTransientState.ContainsKey(stateKey);

        Object currentViewModel = prepareViewModel(pair.Element);
        var stateManager = getStateManager(currentViewModel);
        var initObject = getInitObjectWithSyncContext(currentViewModel);
        var uniqueContextIdAwareModel = tryGetTypedViewModel<IUniqueHostKeyReceiver>(currentViewModel);

        setViewModelUniqueId(uniqueContextIdAwareModel, stateKey);
        
        lastViewModel = selectViewModel(pair.Element, lastViewModel, currentViewModel);

        SendOrPostCallback setDataContext = _ => pair.Element.DataContext = lastViewModel;
        
        if (stateManager != null && hasSavedState)
        {          
          stateManager.LoadState(LastSavedTransientState[stateKey]);
          
        }

        else if (initObject != null)
        {

            initObject.Init();
          
        }
        
          activateAndSetDataContext(currentViewModel, setDataContext);       
        
      }
      
      UIHelper.GetChildren(pair.Element)
              .ToList()
              .ForEach(elementPair => 
                      {
                        elementPair.Parent = pair;
                        restoreTransientState(elementPair, root, lastViewModel);
                      });
      
    }

    private void setViewModelUniqueId(IUniqueHostKeyReceiver viewModel, string stateKey)
    {
      if (viewModel != null)
      {
        viewModel.UniqueHostKey = stateKey;
      }
    }

    private void activateAndSetDataContext(object viewModel, SendOrPostCallback setDataContext)
    {
      notifyActivated(viewModel);
      SynchronizationContext.Current.Send(_ => setDataContext(null), null);
    }

    private object selectViewModel(FrameworkElement element, Object lastViewModel, Object currentViewModel)
    {
      if (currentViewModel != null)
      {
        storeViewModel(currentViewModel);
        return currentViewModel;
      }
      else
      {
        return lastViewModel;
      }


    }

    private void storeViewModel(Object viewModel)
    {
      m_viewModels.Add(viewModel);
    }

    
    private void loadSavedTranzientState()
    {
      
      LastSavedTransientState = null;
      Object savedState;
      
      State.TryGetValue(GetType().FullName, out savedState);
      LastSavedTransientState = savedState as Dictionary<String, Object>;
    }

    private IInitialize getInitObjectWithSyncContext(Object obj)
    {
      var initObject = tryGetTypedViewModel<IInitialize>(obj);

      if (initObject != null)
      {
        initObject.SynchContext = SynchronizationContext.Current;
      }
      return initObject;
    }

    
    private object prepareViewModel(Object obj)
    {
      return ViewModelManager().ResolveViewModel(obj);      
    }

    private ITransientStateManager getStateManager(Object obj)
    {
      var stateManager = obj as ITransientStateManager;
      return stateManager;
    }

    private void PageBase_LayoutUpdated(object sender, EventArgs e)
    {
      handleLayoutUpdated();
    }

    private void handleLayoutUpdated()
    {
      if (WasLayoutUpdateCalled)
      {
        return;
      }


      if (IsNewInstance)
      {
        LoadState();
        IsNewInstance = false;
      }
      else
      {
        handleAllActivated();
      }
      
      WasLayoutUpdateCalled = true;

    }

    private void handleAllActivated()
    {
      m_viewModels.ForEach(notifyActivated);
    }


    private void notifyActivated(object obj)
    {
      var activatedItem = tryGetTypedViewModel<IActivated>(obj);
      if (activatedItem != null)
      {
        activatedItem.Activate();
      }
    }

    private void handleAllDeactivated()
    {
      m_viewModels.ForEach(notifyDeactivated);
    }


    private void notifyDeactivated(object obj)
    {
      var deactivatedItem = tryGetTypedViewModel<IDeactivated>(obj);
      if (deactivatedItem != null)
      {
        deactivatedItem.Deactivate();
      }
    }    
  }
}
